--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / rrc / room_key_crypto.py
Displaying Raw • Download
meshchatx/src/backend/rrc/room_key_crypto.py 3f7ef731b5effd9aa3a81eceea66fd6c2b56498c (3f7ef731) Text, 3.24 KB
T8b949e# SPDX-License-Identifier: 0BSD
T8b949e"""Encrypt RRC room keys at rest with AES-GCM.
Wrapping keys are derived from the active Reticulum identity private key via
HKDF so secrets stay identity-scoped and become unreadable after an identity
switch without copying the private key material into the database.
"""
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
Tff7b72import T7ee787hmac
Tff7b72import T7ee787os
Tff7b72from T7ee787cryptographyT7ee787.T7ee787hazmatT7ee787.T7ee787primitives Tff7b72import Te6edf3hashes
Tff7b72from T7ee787cryptographyT7ee787.T7ee787hazmatT7ee787.T7ee787primitivesT7ee787.T7ee787ciphersT7ee787.T7ee787aead Tff7b72import Te6edf3AESGCM
Tff7b72from T7ee787cryptographyT7ee787.T7ee787hazmatT7ee787.T7ee787primitivesT7ee787.T7ee787kdfT7ee787.T7ee787hkdf Tff7b72import Te6edf3HKDF
Te6edf3_HKDF_SALT Tff7b72= Ta5d6ffbTa5d6ff"Ta5d6ffmeshchatx-rrc-room-keys-v1Ta5d6ff"
Te6edf3_HKDF_INFO Tff7b72= Ta5d6ffbTa5d6ff"Ta5d6ffrrc-room-key-wrapTa5d6ff"
Te6edf3_NONCE_LEN Tff7b72= T79c0ff12
Te6edf3_KEY_LEN Tff7b72= T79c0ff32
Te6edf3MAX_ROOM_KEY_BYTES Tff7b72= T79c0ff256
Tff7b72def Td2a8ffnormalize_room_keyTb4b4b4(Te6edf3room_keyTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
T8b949e"""Strip and validate a room key string for storage or JOIN compare."""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3room_keyTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffroom key must be a stringTa5d6ff"
Tff7b72raise Tf85149TypeErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3key Tff7b72= Te6edf3room_keyTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3keyTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffroom key must not be emptyTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3encoded Tff7b72= Te6edf3keyTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Te6edf3encodedTb4b4b4) Tff7b72> Te6edf3MAX_ROOM_KEY_BYTESTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffroom key must be at most Tffd700{Te6edf3MAX_ROOM_KEY_BYTESTffd700}Ta5d6ff bytesTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72return Te6edf3key
Tff7b72def Td2a8ffroom_keys_equalTb4b4b4(Te6edf3providedTb4b4b4: Tffa657str Tff7b72| Tff7b72NoneTb4b4b4, Te6edf3expectedTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657boolTb4b4b4:
T8b949e"""Constant-time compare of stripped JOIN body against stored hub key."""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3expectedTb4b4b4, Tffa657strTb4b4b4) Tff7b72or Tff7b72not Te6edf3expectedTb4b4b4:
Tff7b72return Tff7b72False
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3providedTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72False
Tff7b72tryTb4b4b4:
Te6edf3left Tff7b72= Te6edf3normalize_room_keyTb4b4b4(Te6edf3providedTb4b4b4)Tff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72except Tb4b4b4(Tf85149TypeErrorTb4b4b4, Tf85149ValueErrorTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72False
Te6edf3right Tff7b72= Te6edf3expectedTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Te6edf3leftTb4b4b4) Tff7b72!= Tffa657lenTb4b4b4(Te6edf3rightTb4b4b4)Tb4b4b4:
Te6edf3hmacTff7b72.Td2a8ffcompare_digestTb4b4b4(Te6edf3leftTb4b4b4, Te6edf3leftTb4b4b4)
Tff7b72return Tff7b72False
Tff7b72return Te6edf3hmacTff7b72.Td2a8ffcompare_digestTb4b4b4(Te6edf3leftTb4b4b4, Te6edf3rightTb4b4b4)
Tff7b72def Td2a8ffderive_wrap_keyTb4b4b4(Te6edf3identity_private_keyTb4b4b4: Tffa657bytesTb4b4b4) Tff7b72-Tff7b72> Tffa657bytesTb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3identity_private_keyTb4b4b4, Tb4b4b4(Tffa657bytesTb4b4b4, Tffa657bytearrayTb4b4b4)Tb4b4b4)Tb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffidentity private key must be bytesTa5d6ff"
Tff7b72raise Tf85149TypeErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72if Tff7b72not Te6edf3identity_private_keyTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffidentity private key must not be emptyTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72return Te6edf3HKDFTb4b4b4(
Te6edf3algorithmTff7b72=Te6edf3hashesTff7b72.Td2a8ffSHA256Tb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3lengthTff7b72=Te6edf3_KEY_LENTb4b4b4,
Te6edf3saltTff7b72=Te6edf3_HKDF_SALTTb4b4b4,
Te6edf3infoTff7b72=Te6edf3_HKDF_INFOTb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffderiveTb4b4b4(Tffa657bytesTb4b4b4(Te6edf3identity_private_keyTb4b4b4)Tb4b4b4)
Tff7b72def Td2a8ffencrypt_room_keyTb4b4b4(Te6edf3identity_private_keyTb4b4b4: Tffa657bytesTb4b4b4, Te6edf3room_keyTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Tffa657tupleTb4b4b4[Tffa657bytesTb4b4b4, Tffa657bytesTb4b4b4]Tb4b4b4:
T8b949e"""Return (nonce, ciphertext) for a room key."""
Te6edf3plaintext Tff7b72= Te6edf3normalize_room_keyTb4b4b4(Te6edf3room_keyTb4b4b4)Tff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Te6edf3wrap_key Tff7b72= Te6edf3derive_wrap_keyTb4b4b4(Te6edf3identity_private_keyTb4b4b4)
Te6edf3nonce Tff7b72= Te6edf3osTff7b72.Td2a8ffurandomTb4b4b4(Te6edf3_NONCE_LENTb4b4b4)
Te6edf3ciphertext Tff7b72= Te6edf3AESGCMTb4b4b4(Te6edf3wrap_keyTb4b4b4)Tff7b72.Td2a8ffencryptTb4b4b4(Te6edf3nonceTb4b4b4, Te6edf3plaintextTb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72return Te6edf3nonceTb4b4b4, Te6edf3ciphertext
Tff7b72def Td2a8ffdecrypt_room_keyTb4b4b4(
Te6edf3identity_private_keyTb4b4b4: Tffa657bytesTb4b4b4,
Te6edf3nonceTb4b4b4: Tffa657bytesTb4b4b4,
Te6edf3ciphertextTb4b4b4: Tffa657bytesTb4b4b4,
Tb4b4b4) Tff7b72-Tff7b72> Tffa657strTb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3nonceTb4b4b4, Tb4b4b4(Tffa657bytesTb4b4b4, Tffa657bytearrayTb4b4b4)Tb4b4b4) Tff7b72or Tffa657lenTb4b4b4(Te6edf3nonceTb4b4b4) Tff7b72!= Te6edf3_NONCE_LENTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffinvalid room key nonceTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3ciphertextTb4b4b4, Tb4b4b4(Tffa657bytesTb4b4b4, Tffa657bytearrayTb4b4b4)Tb4b4b4) Tff7b72or Tff7b72not Te6edf3ciphertextTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffinvalid room key ciphertextTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Te6edf3wrap_key Tff7b72= Te6edf3derive_wrap_keyTb4b4b4(Te6edf3identity_private_keyTb4b4b4)
Te6edf3plaintext Tff7b72= Te6edf3AESGCMTb4b4b4(Te6edf3wrap_keyTb4b4b4)Tff7b72.Td2a8ffdecryptTb4b4b4(Tffa657bytesTb4b4b4(Te6edf3nonceTb4b4b4)Tb4b4b4, Tffa657bytesTb4b4b4(Te6edf3ciphertextTb4b4b4)Tb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72return Te6edf3plaintextTff7b72.Td2a8ffdecodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────